﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Collections.Concurrent;

namespace SharpShare.Storage.Security {
    public sealed class StorageAccessList : IEnumerable<StorageAccessRule> {
        private ConcurrentDictionary<StorageAccessRight, StorageAccessType> _entries = new ConcurrentDictionary<StorageAccessRight, StorageAccessType>();

        public StorageAccessList(StorageAccessList list) {
            if (list != null) {
                this.UnionCore(list);
            }
        }
        public StorageAccessList(IEnumerable<StorageAccessRule> rules = null) {
            if (rules != null) {
                this.UnionCore(rules);
            }
        }
        public StorageAccessList(params StorageAccessRule[] rules)
            : this((IEnumerable<StorageAccessRule>)rules) {

        }

        public static implicit operator StorageAccessList(StorageAccessRule rule) {
            StorageAccessList newList = new StorageAccessList();
            if (rule != null) {
                newList.UnionCore(rule);
            }
            return newList;
        }

        public static bool operator ==(StorageAccessList list1, StorageAccessList list2) {
            if (object.ReferenceEquals(list1, list2)) {
                return true;
            }

            if (object.ReferenceEquals(list1, null) || object.ReferenceEquals(list2, null)) {
                return false;
            }

            return (list1.Equals(list2));
        }
        public static bool operator !=(StorageAccessList list1, StorageAccessList list2) {
            return !(list1 == list2);
        }

        public static StorageAccessList operator +(StorageAccessList list1, StorageAccessList list2) {
            if (list1 == null) {
                list1 = new StorageAccessList();
            }
            if (list2 == null) {
                list2 = new StorageAccessList();
            }

            StorageAccessList newList = list1.Union(list2);
            return newList;
        }
        public static StorageAccessList operator -(StorageAccessList list1, StorageAccessList list2) {
            if (list1 == null) {
                list1 = new StorageAccessList();
            }
            if (list2 == null) {
                list2 = new StorageAccessList();
            }

            StorageAccessList newList = list1.Except(list2);
            return newList;
        }

        public static StorageAccessList operator +(StorageAccessList list) {
            if (list == null) {
                return null;
            }

            return list.Allow();
        }
        public static StorageAccessList operator -(StorageAccessList list) {
            if (list == null) {
                return null;
            }

            return list.Deny();
        }
        public static StorageAccessList operator !(StorageAccessList list) {
            if (list == null) {
                return null;
            }

            StorageAccessList allowList = new StorageAccessList(
                list.Select(r => !r));

            return allowList;
        }

        public int Count { get { return _entries.Count; } }

        public StorageAccessList Union(IEnumerable<StorageAccessRule> rules) {
            StorageAccessList newList = new StorageAccessList(this);
            newList.UnionCore(rules);
            return newList;
        }
        public StorageAccessList Union(StorageAccessRule rule) {
            if (rule == null) {
                return this;
            }

            return Union(new[] { rule });
        }

        public StorageAccessList Except(IEnumerable<StorageAccessRule> rules) {
            StorageAccessList newList = new StorageAccessList(this);
            newList.ExceptCore(rules);
            return newList;
        }
        public StorageAccessList Except(IEnumerable<StorageAccessRight> rights) {
            StorageAccessList newList = new StorageAccessList(this);
            newList.ExceptCore(rights);
            return newList;
        }

        public StorageAccessList Allow() {
            return ChangeType(StorageAccessType.Allow);
        }
        public StorageAccessList Deny() {
            return ChangeType(StorageAccessType.Deny);
        }

        private StorageAccessList ChangeType(StorageAccessType type) {
            StorageAccessList allowList = new StorageAccessList(
                this.Select(r => new StorageAccessRule(type, r.Right)));

            return allowList;
        }

        public StorageAccessType this[StorageAccessRight right] {
            get {
                StorageAccessType type;
                if (!_entries.TryGetValue(right, out type)) {
                    type = StorageAccessType.Neutral;
                }

                return type;
            }
        }

        public bool HasRight(IEnumerable<StorageAccessRight> rights) {
            return rights.All(r => this[r] != StorageAccessType.Deny);
        }
        public bool HasRight(params StorageAccessRight[] rights) {
            return HasRight((IEnumerable<StorageAccessRight>)rights);
        }

        public void DemandRight(IEnumerable<StorageAccessRight> rights) {
            if (!HasRight(rights)) {
                throw new StorageItemUnauthorizedException();
            }
        }
        public void DemandRight(params StorageAccessRight[] rights) {
            DemandRight((IEnumerable<StorageAccessRight>)rights);
        }

        public override string ToString() {
            if (this.Count == 0) {
                return string.Format("No Entries");
            }

            List<string> entryDescs = new List<string>();
            foreach (StorageAccessRule rule in this) {
                entryDescs.Add(rule.ToString());
            }

            return string.Format("{0} Entries: {1}", this.Count, string.Join(", ", entryDescs));
        }

        public override bool Equals(object obj) {
            StorageAccessList other = (obj as StorageAccessList);

            if (other == null) {
                return false;
            }

            bool equal = (other._entries.Equals(this._entries));

            return equal;
        }
        public override int GetHashCode() {
            int code = 0;

            foreach (var right in this) {
                code ^= right.GetHashCode();
            }

            return code;
        }

        #region IEnumerable<StorageAccessRule> Members

        IEnumerator<StorageAccessRule> IEnumerable<StorageAccessRule>.GetEnumerator() {
            var rules = _entries
                .Select(e => new StorageAccessRule(e.Value, e.Key));
            return rules.GetEnumerator();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator() {
            return ((IEnumerable<StorageAccessRule>)this).GetEnumerator();
        }

        #endregion

        private void ExceptCore(StorageAccessRule rule) {
            if (rule.Type == StorageAccessType.Neutral) {
                StorageAccessType removedValue;
                _entries.TryRemove(rule.Right, out removedValue);
                return;
            }

            StorageAccessType existingType;
            if (_entries.TryGetValue(rule.Right, out existingType)) {
                if (existingType == rule.Type) {
                    _entries.TryRemove(rule.Right, out existingType);
                }
            }
        }
        private void ExceptCore(IEnumerable<StorageAccessRule> rules) {
            foreach (var rule in rules) {
                ExceptCore(rule);
            }
        }

        private void ExceptCore(StorageAccessRight right) {
            ExceptCore(new StorageAccessRule(StorageAccessType.Neutral, right));
        }
        private void ExceptCore(IEnumerable<StorageAccessRight> rights) {
            foreach (var right in rights) {
                ExceptCore(right);
            }
        }

        private void UnionCore(StorageAccessRule rule) {
            if (rule == null) {
                return;
            }

            _entries.AddOrUpdate(
                rule.Right,
                r => {
                    return rule.Type;
                },
                (r, existingT) => {
                    if (rule.Type == StorageAccessType.Neutral) {
                        return existingT;
                    }

                    return rule.Type;
                });
        }
        private void UnionCore(IEnumerable<StorageAccessRule> rules) {
            foreach (var rule in rules) {
                this.UnionCore(rule);
            }
        }

        #region Static

        public static readonly StorageAccessList Empty = new StorageAccessList();
        public static readonly StorageAccessList All;
        public static readonly StorageAccessList Modification;

        static StorageAccessList() {
            All = new StorageAccessList(
                System.Enum.GetValues(typeof(StorageAccessRight))
                .Cast<StorageAccessRight>()
                .Select(r => (StorageAccessRule)r));

            Modification = new StorageAccessList(
                 StorageAccessRight.AddContainers,
                 StorageAccessRight.AddFiles,
                 StorageAccessRight.Delete,
                 StorageAccessRight.DeleteChild,
                 StorageAccessRight.ModifyAttributes,
                 StorageAccessRight.ModifyMetadata,
                 StorageAccessRight.WriteData);
        }

        #endregion
    }
}
